package store.lunangangster.engine.impl.agenda;

import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import store.lunangangster.bpmn.model.Activity;
import store.lunangangster.bpmn.model.FlowElement;
import store.lunangangster.bpmn.model.FlowNode;
import store.lunangangster.bpmn.model.Gateway;
import store.lunangangster.bpmn.model.SequenceFlow;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.impl.el.ExpressManager;
import store.lunangangster.engine.impl.entity.ExecutionEntity;
import store.lunangangster.engine.impl.entity.mgr.ExecutionEntityManager;
import store.lunangangster.engine.impl.interceptor.CommandContext;

/**
 * 该操作用于处理两种情况：
 * <ol>
 *   <li>
 *     当前ExecutionEntity为FlowNode时，寻找其合法的OutGoingSequenceFlow，结束当前Execution，
 *     并且新建与合法的OutGoingSequenceFlow数目相等的Executions，为其添加{@link DoElementBehaviorOperation}继续执行
 *   </li>
 *   <li>当前ExecutionEntity为SequenceFlow时，只需要为其添加{@link DoElementBehaviorOperation}即可</li>
 * </ol>
 *
 * <p>{@link DoElementBehaviorOperation}的作用是执行当前{@link FlowElement}的相关行为。详见：
 * {@link DoElementBehaviorOperation}
 *
 * @author cwk
 * @version 1.0
 */
public class TakeOutGoingSequenceFlowOperation extends AbstractOperation {

  private static final Logger log = LoggerFactory.getLogger(TakeOutGoingSequenceFlowOperation.class);

  public TakeOutGoingSequenceFlowOperation(
      ExecutionEntity execution,
      CommandContext commandContext) {
    super(execution, commandContext);
  }

  @Override
  public void run() {

    FlowElement flowElement = execution.getCurrentFlowElement();

    if (flowElement instanceof FlowNode) {
      handleFlowNode((FlowNode) flowElement);
    } else if (flowElement instanceof SequenceFlow) {
      handleSequenceFlow((SequenceFlow) flowElement);
    } else {  // Process end
      handleProcessEnd();
    }
  }

  protected void handleFlowNode(FlowNode flowNode) {
    // FlowNode包含Gateway、Event、Activity
    // Gateway和Activity都有defaultSequenceFlow
    // 以下是对Gateway和Activity的默认行为，具体的实现还需要通过其相关的Behavior表现，
    // 而不应该在TakeOutGoingSequenceFlowOperation中实现
    String defaultSequenceFlowId = null;
    if (flowNode instanceof Gateway) {
      defaultSequenceFlowId = ((Gateway) flowNode).getDefaultSequenceFlow();
    } else if (flowNode instanceof Activity) {
      defaultSequenceFlowId = ((Activity) flowNode).getDefaultSequenceFlow();
    }

    // 确定FlowNode应从哪些SequenceFlow离开
    List<SequenceFlow> legalFlows = new ArrayList<>();
    ExpressManager expressManager = commandContext.getProcessEngineConfiguration()
        .getExpressManager();
    for (SequenceFlow sequenceFlow : flowNode.getOutgoing()) {
      String conditionExpress = sequenceFlow.getConditionExpression();
      if (expressManager.evalExpression(conditionExpress, execution)) {
        legalFlows.add(sequenceFlow);
      }
    }

    // find defaultSequence
    if (legalFlows.isEmpty() && defaultSequenceFlowId != null) {
      for (SequenceFlow sequenceFlow : flowNode.getOutgoing()) {
        if (sequenceFlow.getId().equals(defaultSequenceFlowId)) {
          legalFlows.add(sequenceFlow);
          break;
        }
      }
    }

    // not found illegal outgoing, stop this Execution
    if (legalFlows.isEmpty()) {
      if (flowNode.getOutgoing() == null || flowNode.getOutgoing().isEmpty()) { // final node
        log.debug("节点\"{}\"没有任何出边", flowNode.getId());
        getAgenda().addEndExecutionOperation(execution);
      } else {
        String errorMsg = "节点\"" + flowNode.getId() + "\"没有任何符合条件的出边";
        throw new NodeFlowException(errorMsg);
      }
    } else {
      List<ExecutionEntity> outgoingExecutions = new ArrayList<>(legalFlows.size());
      // 复用execution
      outgoingExecutions.add(execution);
      execution.setCurrentFlowElement(legalFlows.get(0));
      ExecutionEntityManager executionManager = commandContext.getProcessEngineConfiguration()
          .getExecutionEntityManager();
      for (int i = 1; i < legalFlows.size(); i++) {
        ExecutionEntity brotherExecution = executionManager.createBrotherExecution(execution);
        brotherExecution.setCurrentFlowElement(legalFlows.get(i));
        outgoingExecutions.add(brotherExecution);
      }
      // every outgoing need to continue
      for (ExecutionEntity outgoingExecution : outgoingExecutions) {
        getAgenda().addDoElementBehaviorOperation(outgoingExecution, flowNode);
      }
    }
  }


  protected void handleSequenceFlow(SequenceFlow sequenceFlow) {
    getAgenda().addDoElementBehaviorOperation(execution, sequenceFlow);
  }

  protected void handleProcessEnd() {
    getAgenda().addEndExecutionOperation(execution);
  }

}
